﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DocumentManagerAdapter.cs" company="Concept Architecture">
//   (c) 2010 Muhammad Umer Farooq
// </copyright>
// <summary>
//   Defines the DocumentManagerAdapter type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Hisaab.Windows.Client
{
    using System;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Windows;
    using System.Windows.Data;
    using AvalonDock;
    using Common;
    using RibbonWrapper;

    /// <summary>
    /// Defines the <seealso cref="DocumentManagerAdapter"/> type.
    /// </summary>
    public class DocumentManagerAdapter
    {
        #region Data members

        /// <summary>
        /// View model document
        /// </summary>
        private static readonly DependencyProperty ViewModelDocumentProperty =
            DependencyProperty.RegisterAttached("ViewModelDocument", typeof(Document), typeof(DocumentManagerAdapter), null);

        /// <summary>
        /// Docking manager
        /// </summary>
        private readonly DockingManager _avalonDockingManager;

        /// <summary>
        /// Ribon control for document manageer
        /// </summary>
        private readonly IRibbon _ribbon;

        #endregion Data members

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentManagerAdapter"/> class.
        /// </summary>
        /// <param name="dockingManager">The docking manager.</param>
        /// <param name="ribbon">Ribbon control for main form</param>
        public DocumentManagerAdapter(DockingManager dockingManager, IRibbon ribbon)
        {
            Contract.Requires(dockingManager != null, "The master document pane view control must not be null");
            _avalonDockingManager = dockingManager;
            _ribbon = ribbon;

            DocumentManager.Instance.ActiveDocumentChanged += OnDocumentManagerActiveDocumentChanged;

            INotifyCollectionChanged documents = DocumentManager.Instance.Documents;
            documents.CollectionChanged += OnDocumentManagerCollectionChanged;

            dockingManager.PropertyChanged += DockingManagerPropertyChanged;
            dockingManager.DocumentClosed += DockingManagerClosedDocument;
        }

        /// <summary>
        /// Gets the view model document.
        /// </summary>
        /// <param name="viewDoc">The view doc.</param>
        /// <returns>View model of document</returns>
        private static Document GetViewModelDocument(ManagedContent viewDoc)
        {
            Contract.Requires(viewDoc != null, "The view document must not be null to get the view-model document");
            return viewDoc.GetValue(ViewModelDocumentProperty) as Document;
        }

        /// <summary>
        /// Sets the view model document.
        /// </summary>
        /// <param name="viewDoc">The view doc.</param>
        /// <param name="document">The document.</param>
        private static void SetViewModelDocument(ManagedContent viewDoc, Document document)
        {
            Contract.Requires(viewDoc != null, "The view document must not be null to set the view-model document");
            Contract.Requires(document != null, "The view-model document must not be null");
            viewDoc.SetValue(ViewModelDocumentProperty, document);
        }

        /// <summary>
        /// Called when [document manager active document changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="ActiveDocumentChangedEventArgs"/> instance containing the event data.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        private void OnDocumentManagerActiveDocumentChanged(object sender, ActiveDocumentChangedEventArgs args)
        {
            Contract.Requires(sender == DocumentManager.Instance, "The sender must be the view-model's document manager");
            Contract.Requires(args != null, "The active document changed event argument must not be null");

            if (args.PreviouslyActiveDocument != null && !string.IsNullOrWhiteSpace(args.PreviouslyActiveDocument.ContextMenuGroupName))
            {
                var ctxGrp = _ribbon.ContextualTabGroups.Where(
                    x => x.Name == args.PreviouslyActiveDocument.ContextMenuGroupName).FirstOrDefault();

                if (ctxGrp != null)
                {
                    ctxGrp.Visibility = Visibility.Hidden;
                }
            }

            if (args.NewlyActivatedDocuemnt == null)
            {
                return;
            }

            var viewDoc = FindViewControlForViewModelDocument(args.NewlyActivatedDocuemnt);
            Contract.Assert(viewDoc != null, "The active document does not have a view-layer document - this should never happen");

            if (_avalonDockingManager.ActiveDocument != viewDoc)
            {
                _avalonDockingManager.ActiveDocument = viewDoc;
            }

            if (args.NewlyActivatedDocuemnt != null && !string.IsNullOrWhiteSpace(args.NewlyActivatedDocuemnt.ContextMenuGroupName))
            {
                var ctxGrp = _ribbon.ContextualTabGroups.Where(
                    x => x.Name == args.NewlyActivatedDocuemnt.ContextMenuGroupName).FirstOrDefault();

                if (ctxGrp != null)
                {
                    ctxGrp.Visibility = Visibility.Visible;
                }
            }
        }

        /// <summary>
        /// Called when [document manager collection changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Ensures", Justification = "Nothing")]
        private void OnDocumentManagerCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            Contract.Requires(sender == DocumentManager.Instance.Documents, "The sender must be the view-model's DocumentManager.Documents collection");
            Contract.Requires(args != null, "The active document changed event argument must not be null");
            Contract.Requires(args.Action != NotifyCollectionChangedAction.Add || args.NewItems != null, "There should be some new items when the action is 'Add'");
            Contract.Requires(args.Action != NotifyCollectionChangedAction.Remove || args.OldItems != null, "There should be some old items when the action is 'Remove'");
            Contract.Ensures(DocumentManager.Instance.Count == _avalonDockingManager.Documents.Count(), "There is a mismatch between open documents between the adapter and the document manager");

            if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (Document removed in args.OldItems)
                {
                    Contract.Assert(removed != null, "The document that was removed should never be null");
                    var viewDoc = FindViewControlForViewModelDocument(removed);
                    if (viewDoc != null && viewDoc.IsCloseable)
                    {
                        viewDoc.Close();
                    }
                }
            }

            if (args.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (Document added in args.NewItems)
                {
                    Contract.Assert(added != null, "The document that was added should never be null");
                    Contract.Assert(FindViewControlForViewModelDocument(added) == null, "The 'new' document should not already exist in the view");
                    var newViewDoc = CreateNewViewDocument(added);

                    Contract.Assert(_avalonDockingManager.MainDocumentPane != null, "The Avalon docking manager must be configured with a main document pane in the XAML.");
                    _avalonDockingManager.MainDocumentPane.Items.Add(newViewDoc);
                }
            }
        }

        /// <summary>
        /// Finds the view control for view model document.
        /// </summary>
        /// <param name="viewModelDocument">The view model document.</param>
        /// <returns>Document content</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        private DocumentContent FindViewControlForViewModelDocument(Document viewModelDocument)
        {
            Contract.Requires(viewModelDocument != null, "The view-model document to find in the view must not be null");

            Contract.Assume(_avalonDockingManager.Documents != null, "Is there a bug in Avalon docking?");
            var viewDocuments = from d in _avalonDockingManager.Documents
                                where GetViewModelDocument(d) == viewModelDocument
                                select d;
            Contract.Assert(viewDocuments.Count() <= 1, "There should only be at most 1 view document for a view-model document");
            return viewDocuments.FirstOrDefault();
        }

        /// <summary>
        /// Creates the new view document.
        /// </summary>
        /// <param name="viewModelDocument">The view model document.</param>
        /// <returns>Document content</returns>
        private DocumentContent CreateNewViewDocument(Document viewModelDocument)
        {
            Contract.Requires(viewModelDocument != null, "The view-model document to wrap in a view document must not be null");

            var viewDoc = new DocumentContent
                              {
                                  DataContext = viewModelDocument,
                                  Content = viewModelDocument.View
                              };

            var titleBinding = new Binding("DocumentTitle")
            {
                Source = viewModelDocument
            };

            viewDoc.SetBinding(ManagedContent.TitleProperty, titleBinding);

            viewDoc.Closing += OnUserClosingDocument;

            SetViewModelDocument(viewDoc, viewModelDocument);

            return viewDoc;
        }

        /// <summary>
        /// Called when [user closing document].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        private void OnUserClosingDocument(object sender, CancelEventArgs e)
        {
            Contract.Requires(_avalonDockingManager.Documents != null, "The Avalon docking manager documents collection is null, which is bad");
            Contract.Requires(sender as AvalonDock.DocumentContent != null, "The sender must be an Avalon document");

            var viewDoc = sender as DocumentContent;
            var viewModelDoc = GetViewModelDocument(viewDoc);
            Contract.Assert(viewModelDoc != null, "If a document is closing then it should still have an associated view-model document instance");

            DocumentManager.Instance.Close(viewModelDoc);
        }

        /// <summary>
        /// Dockings the manager closed document.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        private void DockingManagerClosedDocument(object sender, EventArgs e)
        {
            int numberOpenDocumentsInView = _avalonDockingManager.Documents.Count;
            int numberOpenDocumentsInViewModel = DocumentManager.Instance.Count;
            Contract.Assert(numberOpenDocumentsInView == numberOpenDocumentsInViewModel, "There is a mismatch between open documents between the adapter and the document manager");
        }

        /// <summary>
        /// Dockings the manager property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Assert", Justification = "Nothing")]
        private void DockingManagerPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            Contract.Requires(sender == _avalonDockingManager, "The sender of this event is not correct");
            Contract.Requires(args != null, "The close document event argument should not be null");
            Contract.Requires(string.IsNullOrEmpty(args.PropertyName) == false, "The name of the property that changed should not be null or empty.");
            Contract.Requires(_avalonDockingManager.Documents != null, "The Avalon docking manager documents collection is null, which is bad");

            if (args.PropertyName == "ActiveDocument")
            {
                Document viewModelDoc = null;

                var activeViewDoc = _avalonDockingManager.ActiveDocument;
                if (activeViewDoc != null)
                {
                    viewModelDoc = GetViewModelDocument(activeViewDoc);
                    Contract.Assert(viewModelDoc != null, "Every view document should be associated with a view model document.");
                }

                int numberOpenDocuments = _avalonDockingManager.Documents.Count;
                if (activeViewDoc == null && numberOpenDocuments > 0)
                {
                    return;
                }

                DocumentManager.Instance.ActiveDocument = viewModelDoc;
            }
        }

        /// <summary>
        /// Invariantses this instance.
        /// </summary>
        [ContractInvariantMethod]
        private void Invariants()
        {
            Contract.Invariant(_avalonDockingManager != null, "The Avalon docking manager must never be null");
        }
    }
}